package algo;

import graph.NetworkPoint;
import graph.GraphEdge;
import graph.GraphNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import rtree.RTreeUtil;
import spatialindex.ISpatialIndex;
import util.Sequencer;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineSegment;

/**
 * Simple implementation of map-matching functions <br>
 * with the help of a in-memory RTree.
 * 
 * @author xiaohui
 * 
 */
public class MapMatcher {

	static RTreeUtil rtreeutil = new RTreeUtil();

	/**
	 * 
	 * @param route
	 * @param numTrials
	 * @return The list of Elocs which each of the route nodes is matched to
	 */
	public List<NetworkPoint> mapMatch(List<GraphNode> gnodes, int numTrials,
			Map<Integer, GraphEdge> edgesMap, ISpatialIndex edgeRTree) {
		List<NetworkPoint> resLocs = new ArrayList<NetworkPoint>();
		for (GraphNode rnode : gnodes) {
			NetworkPoint eloc = mapMatch(rnode,numTrials, edgesMap, edgeRTree);
			resLocs.add(eloc);
		}

		resLocs = basicSmoothening(resLocs);
		return resLocs;
	}

	private List<NetworkPoint> basicSmoothening(List<NetworkPoint> resLocs) {
		// basic smoothening
		NetworkPoint[] elocs = resLocs.toArray(new NetworkPoint[0]);
		int[] eids = new int[resLocs.size()];

		for (int i = 0; i < elocs.length; i++) {
			eids[i] = elocs[i].getEdgeId();
		}

		for (int i = 0; i < eids.length - 2; i++) {
			if ((eids[i] == eids[i + 2]) && (eids[i + 1] != eids[i])) {
				eids[i + 1] = eids[i];
				elocs[i + 1].setEdgeId(eids[i]);
				elocs[i + 1].setRatioFromNode(elocs[i].getRatioFromNode()
						+ elocs[i + 2].getRatioFromNode() / 2);
//				elocs[i + 1].setDistFromNode(elocs[i].getDistFromNode()
//						+ elocs[i + 2].getDistFromNode() / 2);
			}
		}

		return new ArrayList(Arrays.asList(elocs));
	}

	/**
	 * 
	 * @param numTrials
	 * @param gnode
	 * @param edgesMap
	 * @param RTree
	 * @return
	 */
	public static NetworkPoint mapMatch(GraphNode gnode, int numTrials,
			Map<Integer, GraphEdge> edgesMap, ISpatialIndex RTree) {
		double[] point = { gnode.getX(), gnode.getY() };
		NetworkPoint eloc = getExpLocationOnNearestEdge(point, numTrials,
				edgesMap, RTree);
		return eloc;
	}

	/**
	 * 
	 * @param numTrials
	 * @param point
	 * @param edgesMap
	 * @edgeRTree
	 * @return a coordinate its expected location on network
	 */
	private static NetworkPoint getExpLocationOnNearestEdge(double[] point,
			int numTrials, Map<Integer, GraphEdge> edgesMap,
			ISpatialIndex edgeRTree) {
		// 1. index edges with in-memory RTree as line segments <br>
		// 2. do NN search with the point -> netedge <br>
		// 3. return expected location on the netedge <br>

		List<Integer> resultsEdgeIds = rtreeutil.RTreeQuery(edgeRTree,
				numTrials + "NN", point, null);

		// find the edge with shortest distance
		// by scanning NN
		GraphEdge canEdge = null;
		double distSoFar = Double.MAX_VALUE;
		for (int eid : resultsEdgeIds) {
			GraphEdge edge = edgesMap.get(eid);
			double dist = edge.computeDistanceTo(point);
			if (dist < distSoFar) {
				distSoFar = dist;
				canEdge = edge;
			}
		}
		if (canEdge == null) {
			return null;
		}
		Coordinate pFrom = new Coordinate(canEdge.getSrcNode().getX(), canEdge
				.getSrcNode().getY());
		Coordinate pTo = new Coordinate(canEdge.getDestNode().getX(), canEdge
				.getDestNode().getY());
		LineSegment ls = new LineSegment(pFrom, pTo);
		Coordinate p = new Coordinate(point[0], point[1]);
		double ratio = ls.projectionFactor(p);
		// double offset = pFrom.distance(p);
		// return new ELocation(Sequencer.next(), canEdge.getId(), ratio,
		// offset);
		return new NetworkPoint(Sequencer.next(), canEdge.getId(), ratio);

	}

	/**
	 * 
	 * @param node
	 * @param edge
	 * @return the expected location of the node on the edge
	 */
	public NetworkPoint getExpLocation(GraphNode node, GraphEdge edge) {

		Coordinate pFrom = new Coordinate(edge.getSrcNode().getX(), edge
				.getSrcNode().getY());
		Coordinate pTo = new Coordinate(edge.getDestNode().getX(), edge
				.getDestNode().getY());
		LineSegment ls = new LineSegment(pFrom, pTo);
		Coordinate p = new Coordinate(node.getX(), node.getY());
		double ratio = ls.projectionFactor(p);
		// double dist = pFrom.distance(p);
		// return new ELocation(Sequencer.next(), edge.getId(), ratio, dist);
		return new NetworkPoint(Sequencer.next(), edge.getId(), ratio);
	}

}
